package miggy.display;

import miggy.SystemModel;
import miggy.api.config.ConfigurationException;
import miggy.api.config.MachineDefinition;
import miggy.api.cpu.Size;
import miggy.api.display.DisplayController;
import miggy.api.display.VideoMode;
import miggy.api.machine.MachineCore;
import miggy.api.machine.MachineListener;
import miggy.api.machine.MachineState;
import miggy.api.memory.CustomRegisterObserver;
import miggy.memory.CustomRegisters;
import miggy.utils.TextUtil;

import javax.swing.*;
import java.util.logging.Logger;

/*
//  Miggy - Java Amiga MachineCore
//  Copyright (c) 2008, Tony Headford
//  All rights reserved.
//
//  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
//  following conditions are met:
//
//    o  Redistributions of source code must retain the above copyright notice, this list of conditions and the
//       following disclaimer.
//    o  Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
//       following disclaimer in the documentation and/or other materials provided with the distribution.
//    o  Neither the name of the Miggy Project nor the names of its contributors may be used to endorse or promote
//       products derived from this software without specific prior written permission.
//
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
//  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
//  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
//  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
//  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
//  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
//  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// $Revision: 21 $
*/

public class DisplayManager implements DisplayController
{
	private static Logger logger = Logger.getLogger("Miggy.Display");
	private int width;
	private int height;
	private int hstart, hstop, vstart, vstop;
	private boolean hires;
	private int bitplaneCount;
	private boolean hamMode;
	private boolean dualPlayfield;
	private boolean interlaced;
	private int pf1HScroll;
	private int pf2HScroll;
	private int pf1SpritePriority;
	private int pf2SpritePriority;
	private boolean pf2Priority;
	private VideoMode videoMode;
	private int displayLines;
	private int vblLines;
	private Display display;
	private String configName;
	private MachineCore machine;
	private boolean exiting;

	public DisplayManager()
	{
		logger.info("Creating DisplayManager");
		exiting = false;
	}

	/**
	 * Initialise the controller
	 *
	 * @param config
	 * @throws miggy.api.config.ConfigurationException
	 *          is SystemConfig holds invalid information for this component
	 */
	public void init(MachineDefinition config) throws ConfigurationException
	{
		configName = config.getDefinitionName();
		videoMode = config.getVideoMode();

		if(videoMode == VideoMode.NTSC)
		{
			displayLines = 263;
			vblLines = 20;
		}
		else
		{
			displayLines = 313;
			vblLines = 25;
		}

		machine = SystemModel.EMU;
		machine.addListener(new MachineListener() {

			public void stateChange(MachineState state)
			{
				//don't care about state
			}

			public void exit()
			{
				exiting = true;
				display.close();
			}
		});

		initObservers();
		reset();

		//create the window
		SwingUtilities.invokeLater(new Runnable() {
			public void run() { makeDisplay(); }
		});
	}

	public void reset()
	{
		logger.info("Resetting DisplayManager");

		//default setup values
		width = 320;
		hstart = 0x0081;
		vstart = 0x002c;
		hstop = 0x01c1;
		hires = false;
		bitplaneCount = 0;
		hamMode = false;
		dualPlayfield = false;
		interlaced = false;

		if(videoMode == VideoMode.NTSC)
		{
			height = 200;
			vstop = 0x01f4;
		}
		else
		{
			height = 256;
			vstop = 0x012c;
		}

		pf1HScroll = 0;
		pf2HScroll = 0;
		pf1SpritePriority = 0;
		pf2SpritePriority = 0;
		pf2Priority = false;
	}

	private void makeDisplay()
	{
		display = new Display(this, "Miggy - " + configName);
		display.initDisplay(width, height);
	}

	/**
	 * Called by the Display implementation to notify us that it is closing
	 */
	public void displayClosing()
	{
		//TODO: Stop the emulation and quit
		exiting = true;
		SystemModel.EMU.exit();
	}

	public void endFrame()
	{
		//notification that we've reached the end of the frame
		//do whatever's needed here
		//...

		//raise vert blank interrupt
		SystemModel.MEM.poke(CustomRegisters.INTREQ,  0x8020, Size.Word);	//set vbl int
	}

	private void initObservers()
	{
		SystemModel.REGS.addObserver(CustomRegisters.DIWSTRT, new CustomRegisterObserver() {
			public void update(int register, int value, Size size)
			{
				hstart = (value & 0x00ff);
				vstart = (value >> 8) & 0x00ff;
				logger.info("DIWSTRT: " + TextUtil.toHex((short)value));
			}
		});

		SystemModel.REGS.addObserver(CustomRegisters.DIWSTOP, new CustomRegisterObserver() {
			public void update(int register, int value, Size size)
			{
				hstop = (value & 0x00ff) + 0x0100;
				vstop = ((value >> 8) & 0x00ff) + 0x0100;
				logger.info("DIWSTOP: " + TextUtil.toHex((short)value));
			}
		});

		SystemModel.REGS.addObserver(CustomRegisters.BPLCON0, new CustomRegisterObserver() {
			public void update(int register, int value, Size size)
			{
				hires = (value & 0x8000) != 0;
				bitplaneCount = (value >> 12) & 0x0007;
				hamMode = (value & 0x0800) != 0;
				dualPlayfield = (value & 0x0400) != 0;
				interlaced = (value & 0x0004) != 0;

				logger.info("BPLCON0: " + TextUtil.toHex((short)value));
			}
		});

		SystemModel.REGS.addObserver(CustomRegisters.BPLCON1, new CustomRegisterObserver() {
			public void update(int register, int value, Size size)
			{
				pf1HScroll = value & 0x000f;
				pf2HScroll = (value >> 4) & 0x000f;

				logger.info("BPLCON1: " + TextUtil.toHex((short)value));
			}
		});

		SystemModel.REGS.addObserver(CustomRegisters.BPLCON2, new CustomRegisterObserver() {
			public void update(int register, int value, Size size)
			{
				pf1SpritePriority = value & 0x0007;
				pf2SpritePriority = (value >> 3) & 0x000f;
				pf2Priority = (value & 0x0040) != 0;
				logger.info("BPLCON2: " + TextUtil.toHex((short)value));
			}
		});
	}

	public int getWidth()
	{
		return width;
	}

	public int getHeight()
	{
		return height;
	}
}
